# -*- coding: UTF-8 -*-
import traceback
import threading
from copy import deepcopy
from Code.Utils.sc_func import Common, Timer, Singleton, JsonParser
from Code.Model.sc_global import Global
from Code.Model.Data.sc_ssh import SSH, SSHUtil
from Code.Model.Data.sc_data import model_gate, DataInterface, wrapper_exception, TaskDataError
from Code.Model.Controller.sc_loader import loader
from Code.Model.Controller.sc_logger import logger
from Code.Model.Controller.sc_plotter import PlotHandler


@Singleton
class TimerHandler(object):
    def __init__(self):
        self.is_running = True
        self.ssh_timer = None
        self.json_timer = None
        self.task_timer = None
        self.plot_timer = None
        self.last_settings = None
        self.last_widgets = None
        # self.last_trees = None

    def start(self):
        # 工具健康检查定时器
        Timer(func=self.health_check_func, period=60).start()
        # 后台任务定时器 #
        self.task_timer = Timer(func=self.task_timer_func, period=model_gate.settings_data.auto_task_period,
                                do_first=True)
        self.task_timer.start()
        # 后台画图定时器
        self.plot_timer = Timer(func=self.plot_timer_func, period=model_gate.settings_data.auto_plot_period,
                                delay=10, do_first=True)
        self.plot_timer.start()
        # ssh保活定时器 #
        self.ssh_timer = Timer(func=self.ssh_keepalive, period=model_gate.settings_data.keepalive_period)
        self.ssh_timer.start()
        # json配置更新定时器 #
        self.json_timer = Timer(func=self.json_timer_func, period=model_gate.settings_data.refresh_json_period,
                                delay=2, do_first=True)
        self.json_timer.start()

    def health_check_func(self, args=None):
        logger.debug('[HealthTimer] total threads count: {}'.format(len(threading.enumerate())))
        logger.debug('[HealthTimer] active threads count: {}'.format(threading.active_count()))
        logger.debug('[HealthTimer] task_timer.scripts: {}'.format(model_gate.settings_data.auto_task_scripts))
        logger.debug('[HealthTimer] task_timer.period: {}'.format(self.task_timer.period))
        logger.debug('[HealthTimer] plot_timer.period: {}'.format(self.plot_timer.period))
        logger.debug('[HealthTimer] json_timer.period: {}'.format(self.json_timer.period))
        logger.debug('[HealthTimer] ssh_timer.period: {}'.format(self.ssh_timer.period))

    def json_timer_func(self, args=None):
        def compare_settings(key):
            last = self.last_settings.query(key)
            curr = curr_settings.query(key)
            if last != curr:
                logger.info('[JsonTimer] Settings.{} changed, {} -> {}'.format(key, last, curr))
            return last == curr

        logger.debug('[JsonTimer] refresh json data start...')
        if not self.last_settings:
            self.last_settings = deepcopy(model_gate.settings_data)
        if not self.last_widgets:
            self.last_widgets = deepcopy(model_gate.dependence_data.widget_data)
        # if not self.last_trees:
        #    self.last_trees = deepcopy(model_gate.dependence_data.tree_data)
        # 从json文件中重新加载
        if not loader.json_parser(True):
            return
        curr_settings = model_gate.settings_data
        curr_dependence = model_gate.dependence_data
        try:
            compare_settings('log_level')
            logger.change_level(curr_settings.log_level)
            if not compare_settings('tool_alias') or not compare_settings('tool_version'):
                data = '{} v{}'.format(curr_settings.tool_alias, curr_settings.tool_version)
                model_gate.app_title_data.set_data(data)
            if not compare_settings('keepalive_period'):
                self.ssh_timer.update_period(curr_settings.keepalive_period)
            if not compare_settings('refresh_json_period'):
                self.json_timer.update_period(curr_settings.refresh_json_period)
            if not compare_settings('auto_plot_period'):
                self.plot_timer.update_period(curr_settings.auto_plot_period)
            if not compare_settings('auto_task_period'):
                self.task_timer.update_period(curr_settings.auto_task_period)
            compare_settings('auto_task_scripts')

            # if self.last_trees != curr_dependence.tree_data:
            #    print(self.last_trees)
            #    print(curr_dependence.tree_data)
            #    model_gate.app_trees_data.set_data(curr_dependence.tree_data)
            #    logger.info('[JsonTimer] Trees changed')

            if self.last_widgets != curr_dependence.widget_data:
                model_gate.app_widgets_data.set_data(curr_dependence.widget_data)
                logger.info('[JsonTimer] Widgets changed')
        except Exception:
            logger.error('[JsonTimer] Exception: {}'.format(traceback.format_exc()))

        del self.last_settings
        del self.last_widgets
        # del self.last_trees
        self.last_settings = deepcopy(curr_settings)
        self.last_widgets = deepcopy(curr_dependence.widget_data)
        # self.last_trees = deepcopy(curr_dependence.tree_data)
        logger.debug('[JsonTimer] refresh json data end')

    def ssh_keepalive(self, args=None):
        def ssh_check():
            for t in range(1, retry_times + 1):
                ret1 = SSHUtil.user_login(ssh, user)[0]
                ret2 = SSHUtil.user_login(ssh, 'root')[0]
                ret3 = SSHUtil.exec_ret(ssh, 'echo')[0]
                ret4 = SSHUtil.upload_file(ssh, Global.G_SETTINGS_FILE, remote_file)[0]
                if all([ret1, ret2, ret3, ret4]):
                    return True
            return False

        def update_data(state):
            login_state_data[ip]['SSH'] = ssh if state else None
            login_state_data[ip]['STATE'] = 'SUCCESS' if state else 'FAILED'
            model_gate.login_state_data.set_data(login_state_data)

        def update_ui(state):
            if state:
                info = model_gate.query_language_data('RECONNECT', 'SUCCESS')
            else:
                info = '%s, %s' % (model_gate.query_language_data('DISCONNECTED'),
                                   model_gate.query_language_data('RECONNECT'))
            DataInterface.update_infotext_data(ip, 'KEEPALIVE-TIMER', info, state)

        logger.debug('[SSHTimer] ssh keepalive timer start...')
        remote_file = "{}/__keepalive__".format(Global.G_SERVER_UPLOAD % model_gate.settings_data.server_dir)
        retry_times = model_gate.settings_data.retry_times
        port = model_gate.settings_data.sshd_port
        login_state_data = model_gate.login_state_data.get_data()
        rebuild_ips = []
        for ip, data in login_state_data.items():
            ssh = data['SSH']
            user, upwd, rpwd = data['PWD']
            if ssh_check():
                update_data(True)
                continue
            logger.warn("[SSHTimer] ssh instance of {} is invalid, rebuild now".format(ip))
            if ssh:
                ssh.close()
                del ssh
            update_data(False)
            update_ui(False)
            rebuild_ips.append((ip, user, upwd, rpwd))
        # 重建SSH实例 #
        for ip, user, upwd, rpwd in rebuild_ips:
            ssh = SSH(ip, port, user, upwd, rpwd)
            if ssh_check():
                logger.info("[SSHTimer] rebuild ssh instance of {} success".format(ip))
                update_data(True)
                update_ui(True)
        logger.debug('[SSHTimer] ssh keepalive end, login_state_data: {}'.format(login_state_data))

    @wrapper_exception
    def task_timer_func(self, args=None):
        logger.debug('[TaskTimer] task timer start...')
        scripts = model_gate.settings_data.auto_task_scripts
        for ip, data in model_gate.login_state_data.get_data().items():
            ssh = data['SSH']
            if not ssh:
                raise TaskDataError('[TaskTimer] {} SSH {}'.format(ip, model_gate.query_language_data('DISCONNECTED')))
            for script in scripts:
                SSHUtil.exec_ret(ssh, "killall {}".format(script), True)
                types = 'python' if script.endswith('.py') else 'sh'
                cmd = "{} {}/{}".format(types, model_gate.settings_data.server_dir, script)
                ret, out, err = SSHUtil.execute(ssh, cmd, True)
                logger.debug("[TaskTimer] {} {} out: {}".format(ip, script, out))
                if not ret:
                    logger.error("[TaskTimer] {} {} err: {}".format(ip, script, str(err)))
                    # raise TaskDataError('[TaskTimer] {} {} err: {}'.format(ip, script, str(err)))
        logger.debug('[TaskTimer] task timer end')

    @wrapper_exception
    def plot_timer_func(self, args=None):
        logger.debug('[PlotTimer] plot timer start...')
        for ip, data in model_gate.login_state_data.get_data().items():
            ssh = data['SSH']
            if self.is_running and not ssh:
                raise TaskDataError('[PlotTimer] {} SSH {}'.format(ip, model_gate.query_language_data('DISCONNECTED')))
            cmd = '{}/{} auto_download_task'.format(model_gate.settings_data.server_dir, Global.G_FUNC_CALLER)
            ret, out, err = SSHUtil.execute(ssh, cmd, True)
            if self.is_running and not out:
                logger.error('[PlotTimer] {} auto_download_task err: {}'.format(ip, str(err)))
                raise TaskDataError('[PlotTimer] {} {}'.format(ip, str(err)))
            local_path = "{}\\{}".format(Global.G_DOWNLOAD_DIR, ip)
            Common.mkdir(local_path)
            server_pack = out.split(Global.G_INNER_SPLIT)[1].strip()
            local_file = '{}\\{}'.format(local_path, Common.basename(server_pack))
            ret, err = SSHUtil.download_file(ssh, server_pack, local_file)
            if self.is_running and not ret:
                raise TaskDataError('[PlotTimer] {} {}'.format(ip, str(err)))
            unzip_dir = '{}\\{}'.format(local_path, Common.basename(local_file).split('.')[0])
            Common.unzip_file(local_file, unzip_dir)
            self.make_plots(ip, unzip_dir)
        logger.debug('[PlotTimer] plot timer end')

    @classmethod
    def make_plots(cls, ip, unzip_dir):
        Common.mkdir(PlotHandler.get_plot_image_path(ip))
        auto_plot_dir = '%s\\PLOTS' % unzip_dir
        auto_plot_conf = '%s\\plots.json' % auto_plot_dir
        if not Common.is_dir(auto_plot_dir):
            logger.warn("[PlotTimer] {} No such directory: 'PLOTS'".format(ip))
            return
        if not Common.is_file(auto_plot_conf):
            logger.warn("[PlotTimer] {} No such file: 'plots.json'".format(ip))
            return
        try:
            data = JsonParser.load(auto_plot_conf)
        except:
            logger.error("[PlotTimer] {} JSONDecodeError for 'plots.json'".format(ip))
            return
        for file in Common.list_dir(auto_plot_dir)[2]:
            if file == 'plots.json':
                continue
            if not file.endswith('.csv'):
                logger.warn("[PlotTimer] {} {} is not CSV file, ignored".format(ip, file))
                continue
            csv_path = '%s\\%s' % (auto_plot_dir, file)
            params = data[file] if file in data else []
            logger.debug("[PlotTimer] {} make plot {} to {}, params: {} ".format(ip, file, file, params))
            PlotHandler.make_picture((ip, csv_path, params))

    def close(self, data=None):
        self.is_running = False
        self.ssh_timer.stop()
        self.json_timer.stop()
        self.task_timer.stop()
        self.plot_timer.stop()


timer_handler = TimerHandler()
